Skip to content

refactor: move project manager to service layer#68

Merged
AgentWrapper merged 13 commits into
mainfrom
feat/project-manager-store
Jun 1, 2026
Merged

refactor: move project manager to service layer#68
AgentWrapper merged 13 commits into
mainfrom
feat/project-manager-store

Conversation

@neversettle17-101
Copy link
Copy Markdown
Collaborator

@neversettle17-101 neversettle17-101 commented Jun 1, 2026

Summary

Rebases the project-manager cleanup onto latest main and aligns service code under resource-specific service packages.

  • removes the process-local project MemoryStore / NewMemoryManager
  • moves controller-facing project business logic from backend/internal/project into backend/internal/service/project
  • moves session service code into backend/internal/service/session
  • moves PR observation manager into backend/internal/service/pr
  • deletes the old backend/internal/project package entirely
  • adds domain.ProjectRecord as the durable storage row shape shared by service and sqlite without creating service↔storage cycles
  • updates sqlite project storage to expose explicit project persistence methods (ListProjects, GetProject, FindProjectByPath, UpsertProject, ArchiveProject)
  • keeps HTTP controllers depending on resource service contracts, not concrete storage
  • trims project routes to the currently implemented surface: list, add, get, delete
  • preserves the session routes merged in refactor: move session display status to service layer #67 while extending the code-first OpenAPI generator to cover both project and session routes
  • regenerates openapi.yaml

Notes

The old import-cycle concern is handled by putting the persistence row in domain and the service-facing store interface in service/project; sqlite implements that interface implicitly. service/project does not import sqlite, and sqlite does not import service/project.

Testing

  • npm run lint

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jun 1, 2026

Greptile Summary

This PR moves project management from a process-local MemoryStore into a proper service layer at backend/internal/service/project, aligning it with the session service structure introduced in #67. It introduces domain.ProjectRecord as the shared storage row shape, exposes explicit persistence methods on the sqlite store, trims the project HTTP surface to the four implemented routes, and regenerates openapi.yaml to cover both project and session endpoints.

  • service/project.Service correctly guards archived rows in Get and Add using ArchivedAt.IsZero(), and the SQL source for ArchiveProject was fixed with AND archived_at IS NULL to prevent double-delete returning 200 — but sqlc generate was not re-run for that query, so the compiled archiveProject constant in gen/projects.sql.go still lacks the guard (see inline comment).
  • FindProjectByPath was correctly re-generated with its AND archived_at IS NULL guard, and the UpsertProject ON CONFLICT path correctly clears archived_at to NULL when re-registering a previously removed project.
  • Controller, daemon wiring, and test suite are all updated to depend on the new service interface rather than the old in-memory manager.

Confidence Score: 4/5

Safe to merge after regenerating the sqlc output — the SQL source is correct but the compiled constant for ArchiveProject is one generation behind.

The service logic, archived-row filtering, and re-add-after-remove flow are all correct. The one concrete defect is the archiveProject constant in gen/projects.sql.go: the SQL source was patched with AND archived_at IS NULL but sqlc generate was not re-run for that query, so a second DELETE on the same project still returns 200 OK at runtime. Everything else — the domain type, store interface, controller wiring, and test coverage — looks sound.

backend/internal/storage/sqlite/gen/projects.sql.go — the archiveProject constant needs to be regenerated to pick up the AND archived_at IS NULL guard that is already present in projects.sql.

Important Files Changed

Filename Overview
backend/internal/service/project/service.go New service layer implementing Manager interface; Get and Add correctly guard archived rows with ArchivedAt.IsZero() checks; Remove properly detects not-found via ArchiveProject return value.
backend/internal/storage/sqlite/gen/projects.sql.go Generated file is partially out of sync: FindProjectByPath was updated with AND archived_at IS NULL, but archiveProject constant still lacks the guard added to projects.sql — double-delete returns 200 instead of 404 at runtime.
backend/internal/storage/sqlite/queries/projects.sql SQL source is correct: FindProjectByPath and ArchiveProject both have AND archived_at IS NULL guards; the source is ahead of the generated file.
backend/internal/httpd/controllers/dto.go New file defining wire shapes for project and session routes; ProjectOrDegraded discriminated union marshals correctly; newGetProjectResponse pre-validates before committing the 200 status.
backend/internal/httpd/controllers/projects.go Controller now depends on service/project.Manager; removes reload/repair/updateConfig routes; correctly handles empty GetResult with a 500 before writing the 200 status.
backend/internal/storage/sqlite/store/project_store.go Store methods renamed and updated to use domain.ProjectRecord; nullTime helper correctly maps zero time.Time to SQL NULL, enabling UpsertProject to un-archive rows.
backend/internal/service/project/service_test.go Tests use real sqlite store and cover add/list/get/remove, re-add after remove (same path and same id), validation, and conflict detection; Get-after-Remove correctly asserts 404 now.
backend/internal/domain/project.go New shared domain type ProjectRecord breaks the old import cycle between project and sqlite packages; straightforward struct with no logic.

Sequence Diagram

sequenceDiagram
    participant C as HTTP Controller
    participant S as service/project.Service
    participant ST as sqlite Store
    participant DB as SQLite DB

    Note over C,DB: Add project
    C->>S: Add(ctx, AddInput)
    S->>ST: FindProjectByPath(path)
    ST->>DB: "SELECT WHERE path=? AND archived_at IS NULL"
    DB-->>ST: ErrNoRows
    ST-->>S: "ok=false"
    S->>ST: GetProject(id)
    ST->>DB: "SELECT WHERE id=?"
    DB-->>ST: row, ok
    Note over S: if ok and ArchivedAt.IsZero() and path differs, return ID conflict
    S->>ST: UpsertProject(record with zero ArchivedAt)
    ST->>DB: "INSERT ON CONFLICT DO UPDATE SET archived_at=NULL"
    S-->>C: "Project{}"

    Note over C,DB: Remove project
    C->>S: Remove(ctx, id)
    S->>ST: ArchiveProject(id, now)
    ST->>DB: "UPDATE SET archived_at=? WHERE id=?"
    Note over DB: Missing AND archived_at IS NULL in gen file
    DB-->>ST: "rowsAffected=1 even if already archived"
    ST-->>S: "ok=true"
    S-->>C: RemoveResult 200 OK

    Note over C,DB: Get project
    C->>S: Get(ctx, id)
    S->>ST: GetProject(id)
    ST->>DB: "SELECT WHERE id=?"
    DB-->>ST: row, ok
    Note over S: if not ok or not ArchivedAt.IsZero() return PROJECT_NOT_FOUND
    S-->>C: "GetResult{Status:ok, Project}"
Loading

Reviews (8): Last reviewed commit: "refactor: move pr manager into service l..." | Re-trigger Greptile

Comment thread backend/internal/service/project/service_test.go
Comment thread backend/internal/service/project/service_test.go
Comment thread backend/internal/storage/sqlite/queries/projects.sql Outdated
@neversettle17-101 neversettle17-101 requested review from illegalcall and removed request for Vaibhaav-Tiwari June 1, 2026 12:55
@Vaibhaav-Tiwari
Copy link
Copy Markdown
Collaborator

go test ./... is currently failing on PR 68 in backend/internal/httpd/apispec/specgen: the drift test says the embedded backend/internal/httpd/apispec/openapi.yaml is stale compared with fresh specgen.Build() output. I confirmed that running go generate ./... from the backend/ directory regenerates the spec and makes go test ./internal/httpd/apispec/specgen pass, so the fix is to regenerate and commit openapi.yaml. Since this surfaced on a Windows checkout, it may also be worth adding a .gitattributes rule such as backend/internal/httpd/apispec/openapi.yaml text eol=lf, or normalizing line endings in the test, so the byte-for-byte drift check does not fail across Windows/Linux checkouts

neversettle17-101 and others added 10 commits June 1, 2026 23:59
…ory store

The project Manager now runs only against the durable backend store: remove the
process-local MemoryStore (and NewMemoryManager), and require a real Store. The
daemon already wires the sqlite store; tests now build a real temp-dir sqlite
store instead of the mock.

- Move Row + the Store port to project/store.go. The Store interface stays
  because it is the dependency-inversion port that lets the manager reach the
  backend without an import cycle (storage imports project.Row), not an extra
  mock layer — there is no longer any in-memory implementation.
- NewManager requires a non-nil Store (no in-memory fallback).
- Add project/manager_test.go: List/Add/Get/Remove happy paths +
  PATH_REQUIRED/NOT_A_GIT_REPO/PATH_ALREADY_REGISTERED/ID_ALREADY_REGISTERED,
  PROJECT_NOT_FOUND/INVALID_PROJECT_ID, and UpdateConfig — all against a real
  sqlite store (the service-logic tests #47 lacked).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…penAPI

- Remove POST /reload, PATCH /{id}, POST /{id}/repair routes and their
  Manager methods (Reload, UpdateConfig, Repair) and DTOs (ReloadResult,
  UpdateConfigInput) — not needed at this stage
- Merge Manager interface into manager.go; delete project.go (single-impl
  split served no purpose)
- Remove dead notImplemented helper from errors.go
- Port PR #59 code-first OpenAPI generation: controllers/dto.go named
  response types, specgen/build.go (4 routes), parity + drift tests,
  cmd/genspec, go generate wiring; regenerate openapi.yaml
- Add swaggest deps; add YAML() method to apispec.Spec

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- t.Skipf → t.Fatalf in gitRepo helper: git failures now hard-fail
  instead of silently skipping manager tests on a misconfigured runner
- FindProjectByPath: add AND archived_at IS NULL so archived paths don't
  permanently block re-registration (update queries/projects.sql and
  generated gen/projects.sql.go)
- Add TestManager_ReaddAfterRemove to lock the fix

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
@AgentWrapper AgentWrapper force-pushed the feat/project-manager-store branch from 3358265 to b940df4 Compare June 1, 2026 18:37
@AgentWrapper AgentWrapper changed the title refactor(project): manager talks to the sqlite store; drop the in-memory store refactor: move project manager to service layer Jun 1, 2026
Comment thread backend/internal/service/project/service.go Outdated
@AgentWrapper AgentWrapper merged commit 3a93e33 into main Jun 1, 2026
7 checks passed
yyovil added a commit that referenced this pull request Jun 2, 2026
Re-integrate the agent-adapter layer onto main's rewritten session lane.

main (#62/#66/#67/#68/#69) moved the session manager into session_manager
plus a service layer, reworked the domain (Activity, IsTerminated), and
removed the tmux runtime. This merge:

- Keeps the rich, hooks-capable ports.Agent (in internal/ports) as the
  canonical agent contract; session_manager.Manager now resolves a real
  adapter per session via ports.AgentResolver (from cfg.Harness on Spawn,
  the stored harness on Restore) and builds RuntimeConfig.Argv.
- Drops the tmux runtime; standardizes on zellij.
- Re-adds the daemon's per-session agent resolver wiring
  (buildAgentResolver over the claude-code + codex registry, AO_AGENT
  default), ready to plug into the httpd session-route slot.

go build, go vet, and go test -race are green; the zellij/tmux real
integration tests are environment-gated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants